﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Utils.APIClient;

namespace Utils.APIClient
{
    public class APIClient
    {
        public string ServerUrl { get; set; }

        public static Dictionary<Type, RemoteServuceSetting> RemoteServuceSettings = new Dictionary<Type, RemoteServuceSetting>();

        static APIClient()
        {
            foreach (var t in FindImplementedTypes(typeof(BaseReqeust<BaseResponseData>), null))
            {
                var remoteServuceSetting = new RemoteServuceSetting();
                RemoteServuceSettings.Add(t, remoteServuceSetting);
                remoteServuceSetting.RemoteService = t.GetCustomAttributes(typeof(RemoteServiceAttribute), false).FirstOrDefault() as RemoteServiceAttribute;
                AnalyseUrlParameter(t, remoteServuceSetting);
                remoteServuceSetting.FilePropertys = t.GetProperties().Where(r => r.PropertyType == typeof(FileItem));
            }
        }

        public APIClient(string serverUrl)
        {
            ServerUrl = serverUrl.TrimEnd('/');
        }
        public virtual BaseResponse<T> Execute<T>(BaseReqeust<T> req) where T : BaseResponseData, new()
        {
            var rsp= ExecuteInternal(req);
            return rsp;
        }

        protected virtual BaseResponse<T> ExecuteInternal<T>(BaseReqeust<T> req) where T : BaseResponseData, new()
        {
            RemoteServuceSetting remoteServuceSetting = RemoteServuceSettings[req.GetType()];
            var url = BuildUrl(req, remoteServuceSetting);
#if DEBUG == false
            try
            {
#endif           
            var client = WebRequestExtension.CreateDefault(url);
            client.Method = remoteServuceSetting.RemoteService.Method;
            client.ContentType = "application/json";
            var requestBody = GetReqeustBody(req, client);
            var files = GetRequestFiles(req, remoteServuceSetting);
            client.SubmitFormData(requestBody, files);
            var rsp = client.GetResponseIgnoreServerError();
            BaseResponse<T> rspModel = new BaseResponse<T>()
            {
                HttpWebResponse = rsp
            };
            if (typeof(FileItem).IsAssignableFrom(typeof(T)))
            {
                var file = new T() as FileItem;
                file.Stream = GetSaveFileStream(req, client);
                rsp.GetResponseFileItem(file);
                rspModel.Data = file as T;
            }
            else
            {
                rspModel.ResponseString = rsp.GetResponseString();
                rspModel.Data = GetResponse(rspModel.ResponseString, rsp, req, requestBody, files, client);
            }
            return rspModel;
#if DEBUG == false
        }
            catch (Exception e)
            {
                return OnError(e, req, url);
            }
#endif
        }

        protected virtual T GetResponse<T>(string rspStr, HttpWebResponse rsp, BaseReqeust<T> req, object requestBody, IDictionary<string, FileItem> files, WebRequest client) where T : BaseResponseData, new()
        {
            var data = JsonConvert.DeserializeObject<T>(rspStr);
            return data;
        }
        protected virtual object GetReqeustBody<T>(BaseReqeust<T> req, WebRequest client) where T : BaseResponseData, new()
        {
            var reqStr = JsonConvert.SerializeObject(req, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            if (reqStr == "{}") reqStr = null;
            return reqStr;
        }
        protected virtual Stream GetSaveFileStream<T>(BaseReqeust<T> req, WebRequest client) where T : BaseResponseData, new()
        {
            return new MemoryStream();
        }

        protected virtual BaseResponse<T> OnError<T>(Exception e, BaseReqeust<T> req, string url) where T : BaseResponseData, new()
        {
            throw e;
        }

        private string BuildUrl<T>(BaseReqeust<T> req, RemoteServuceSetting remoteServuceSetting) where T : BaseResponseData, new()
        {
            remoteServuceSetting = RemoteServuceSettings[req.GetType()];
            var url = remoteServuceSetting.RemoteService.Url;
            foreach (var urlProperty in remoteServuceSetting.UrlPropertys)
            {
                url = url.Replace("{" + urlProperty.Name + "}", urlProperty.GetValue(req, null)?.ToString());
            }
            url = ServerUrl + url;
            return url;
        }
        private IDictionary<string, FileItem> GetRequestFiles<T>(BaseReqeust<T> req, RemoteServuceSetting remoteServuceSetting) where T : BaseResponseData, new()
        {
            IDictionary<string, FileItem> files = null;
            if (remoteServuceSetting.FilePropertys.Count() > 0)
            {
                files = new Dictionary<string, FileItem>();
                foreach (var fileProperty in remoteServuceSetting.FilePropertys)
                {
                    var file = fileProperty.GetValue(req, null) as FileItem;
                    files.Add(fileProperty.Name, file);
                }
            }
            return files;
        }


        public class RemoteServuceSetting
        {
            public RemoteServiceAttribute RemoteService { get; set; }

            public List<PropertyInfo> UrlPropertys { get; set; }

            public IEnumerable<PropertyInfo> FilePropertys { get; set; }

        }
        private static void AnalyseUrlParameter(Type type, RemoteServuceSetting remoteServuceSetting)
        {
            remoteServuceSetting.UrlPropertys = new List<PropertyInfo>();
            var queue = new Queue<char>();
            var isParameStart = false;
            foreach (var c in remoteServuceSetting.RemoteService.Url)
            {
                if (c == '{')
                {
                    isParameStart = true;
                    queue.Clear();
                }
                else if (c == '}')
                {
                    isParameStart = false;
                    var propertyName = string.Join(null, queue);
                    var property = type.GetProperty(propertyName);
                    remoteServuceSetting.UrlPropertys.Add(property);
                }
                else if (isParameStart)
                {
                    queue.Enqueue(c);
                }
            }
            if (remoteServuceSetting.RemoteService.Url[0] != '/') remoteServuceSetting.RemoteService.Url = "/" + remoteServuceSetting.RemoteService.Url;
        }

        private static IEnumerable<Type> FindImplementedTypes(Type BaseType, string FindNameSpace)
        {
            Assembly[] ass = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly a in ass)
            {

                foreach (var t in a.GetTypes())
                {
                    if (FindNameSpace == null || (t.Namespace != null && t.Namespace.StartsWith(FindNameSpace)))
                    {

                        if (t != BaseType && IsAssignableToType(t, BaseType))
                        {
                            yield return t;
                        }
                    }
                }
            }
        }
        private static bool IsAssignableToType(Type childType, Type BaseType)
        {
            try
            {
                if (childType == BaseType)
                {
                    return true;
                }

                if (childType.IsGenericType && BaseType.IsGenericType && childType.GetGenericTypeDefinition() == BaseType.GetGenericTypeDefinition())
                {
                    var childArguments = childType.GetGenericArguments();
                    var baseArguments = BaseType.GetGenericArguments();
                    if (childArguments.Length == baseArguments.Length)
                    {
                        var i = 0;
                        for (; i < childArguments.Length; i++)
                        {
                            if (IsAssignableToType(childArguments[i], baseArguments[i]) == false) break;
                        }
                        if (i >= childArguments.Length) return true;
                    }
                }

                foreach (var interfaceType in childType.GetInterfaces())
                {
                    if (IsAssignableToType(interfaceType, BaseType))
                    {
                        return true;
                    }
                }

                if (childType.FullName == null || childType.BaseType == null)
                {
                    return false;
                }
                return IsAssignableToType(childType.BaseType, BaseType);
            }
            catch (Exception e)
            {
                e.ToString();
                throw;
            }
        }
    }
}
